﻿# define _CRT_SECURE_NO_WARNINGS 1
//1. 字符指针变量
//在指针的类型中我们知道有⼀种指针类型为字符指针 char*; 
//⼀般使⽤:
#include <stdio.h>
//int main()r
//{
//	char ac = 'w';
//	char* pr = &ac;
//	*pr = 'a';
//	printf("%c", ac);
//	return 0;
//}
//还有⼀种使⽤⽅式如下：
//int main()
//{
//	const char* pr = "hello world";
//	printf("%c", *pr);
//	return 0;
//}
//代码 const char* pr = "hello bit."; 特别容易让同学以为是把字符串hello world放
//到字符指针 pr ⾥了，但是本质是把字符串 hello world⾸字符的地址放到了pr中。
//int main()
//{
//	char str1[] = "hello bit.";
//	char str2[] = "hello bit.";
//	const char* str3 = "hello bit.";
//	const char* str4 = "hello bit.";
//	if (str1 == str2)
//		printf("str1 and str2 are same\n");
//	else
//		printf("str1 and str2 are not same\n");
//
//	if (str3 == str4)
//		printf("str3 and str4 are same\n");
//	else
//		printf("str3 and str4 are not same\n");
//
//	return 0;
//}
//输出结果：
//str1 and str2 are not same
//str3 and str4 are same
//这⾥str3和str4指向的是⼀个同⼀个常量字符串。C / C++会把常量字符串存储到单独的⼀个内存区域，
//当⼏个指针指向同⼀个字符串的时候，他们实际会指向同⼀块内存。但是⽤相同的常量字符串去初始
//化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同，str3和str4相同。
//2. 数组指针变量 
//int (*p)[10];
//int main()
//{
//	int arr[10] = { 0 };
//	int(*p) = &arr;
//	return 0;
//}
//数组指针类型解析：
//int (*p)[10] = &arr;
//[10]：p指向数组的元素个数；
//p：是数组指针变量名
//int ：p指向数组的元素类型
//3. ⼆维数组传参的本质
//void test(int arr[3][5], int r, int c)
//{
//	int i = 0;
//	int j = 0;
//	for (i = 0; i < r; i++)
//	{
//		for (j = 0; j < c; j++)
//		{
//			printf("%d ", arr[i][j]);
//		}
//		printf("\n");
//	}
//}
//int main()
//{
//	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,5 };
//	test(arr, 3, 5);
//	return 0;
//}
//⼆维数组传参本质上也是传递了地址，传递的是第⼀
//⾏这个⼀维数组的地址，那么形参也是可以写成指针形式的。如下：
//void test(int (*p)[5], int r, int c)
//{
//	int i = 0;
//	int j = 0;
//	for (i = 0; i < r; i++)
//	{
//		for (j = 0; j < c; j++)
//		{
//			printf("%d ", *(*(p+i)+j));
//		}
//		printf("\n");
//	}
//}
//int main()
//{
//	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,5 };
//	test(arr, 3, 5);
//	return 0;
//}
//总结：⼆维数组传参，形参的部分可以写成数组，也可以写成指针形式。
//4. 函数指针变量
//4.1 函数指针变量的创建
//void test(int a)
//{
//	if (a > 5)
//	{
//		printf("hehe\n");
//	}
//}
//int main()
//{
//	int a = 10;
//	test(a);
//	printf("test: %p\n", test);
//	printf("&test: %p\n", &test);
//	return 0;
//}
//输出结果: 
//hehe
//test : 00007FF69FE013C0
//& test : 00007FF69FE013C0
//可以看出确实打印出来了地址，所以函数是有地址的，函数名就是函数的地址，当然也可以通过 & 函数名 的⽅
//式获得函数的地址。
//如果我们要将函数的地址存放起来，就得创建函数指针变量咯，函数指针变量的写法其实和数组指针
//⾮常类似。如下：
//void test()
//{
//	printf("hehe");
//}
//int add(int a, int b)
//{
//	return a + b;
//}
//int main()
//{
//	void (*p1)() = test;
//	void (*p2)() = &test;
//	int (*p3)(int, int) = add;
//	int (*p4)(int x, int y) = &add;//x和y写上或者省略都是可以的 
//	printf("%p\n", p1);
//	printf("%p\n", p2);
//	printf("%p\n", p3);
//	printf("%p\n", p4);
//}
//输出结果：
//00007FF63FC713C0
//00007FF63FC713C0
//00007FF63FC713D9
//00007FF63FC713D9
//函数指针变量解析：
//int (*p4)(int x, int y) = &add;
//int:函数指针p4变量指向函数的返回类型
//(*p4)：函数指针变量名
//(int x, int y)：函数指针p4指向函数的参数类型和个数的交代
//4.2 函数指针变量的使⽤
//通过函数指针调⽤指针指向的函数。
//int add(int a, int b)
//{
//	return a + b;
//}
//int main()
//{
//	int (*p)(int x, int y) = &add;//x和y写上或者省略都是可以的 
//	int c = p(9, 3);
//	int b = (*p)(6, 3);
//	printf("%d\n", c);
//	printf("%d\n", b);
//}
//4.3 两段有趣的代码
//代码1
//1 (*(void (*)())0)();
//代码解析：
//1. 上述代码其实是⼀次函数调⽤，调⽤的是0地址处的⼀个函数，这个函数没有参数，没有返回值。
//2. 代码中的 void (*)() 是函数指针类型，(void (*)())0 类型放在括号中意思是强制类型转
//化，是将0这个整型值，强制类型转化成这种函数指针类型，也就是说0被当做函数的地址了。
//3. * (void (*)())0 ，前⾯加⼀个 * ，就是调⽤0地址处的这个函数，根据函数指针的类型能知
//道，这个函数没有参数，也没有返回值。
//代码2
//1 void (*signal(int, void(*)(int)))(int);
//代码解析：
//1. 上述代码是⼀次函数的声明。
//2. 声明的函数名字叫 signal ，函数的参数有2个，第⼀个是int类型，第⼆个是函数指针类
//型 : void(*)(int) 。函数的返回值也是函数指针 void(*)(int) 。
//.3.1 typedef关键字
//typedef是⽤来类型重命名的，可以将复杂的类型，简单化。
//⽐如，你觉得 unsigned int 写起来不⽅便，如果能写成 uint 就⽅便多了，那么我们可以使⽤：
//typedef unsigned int uint;
////将unsigned int 重命名为uint 
//如果是指针类型，能否重命名呢？其实也是可以的，⽐如，将 int* 重命名为 ptr_t, 这样写：
//1 typedef int* ptr_t;
//int main()
//{
//	typedef int* ptr_t;
//	int a = 10;
//	int b = 6;
//	int* p = &a;
//	ptr_t p1 = &b;
//	return 0;
//}
//但是对于数组指针和函数指针稍微有点区别：
//⽐如我们有数组指针类型 int(*)[5], 需要重命名为 parr_t ，那可以这样写：
//1 typedef int(*parr_t)[5]; //新的类型名必须在*的右边 
//int main()
//{
//	int arr[10] = { 0 };
//	int (*p)[10] = &arr;
//	typedef int (*parr_t)[10];
//	int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	parr_t p1 = arr1;
//	return 0;
//}
//函数指针类型的重命名也是⼀样的，⽐如，将 void(*)(int) 类型重命名为 pf_t, 就可以这样写：
//1 typedef void(*pfun_t)(int);//新的类型名必须在*的右边 
//int add(int a, int b)
//{
//		eturn a + b;
//}
//typedef int(*parr_t)(int x, int y);
//int main()
//{
//	int (*p1)(int x, int y) = add;
//	parr_t p2 = add;
//	printf("%p\n", p1);
//	printf("%p\n", p2);
//	return 0;
//}
//5. 函数指针数组
//数组是⼀个存放相同类型数据的存储空间，我们已经学习了指针数组，
//⽐如：
//int* arr[10];
////数组的每个元素是int* 
//int* arr[10];
//数组的每个元素是int* 

//那要把函数的地址存到⼀个数组中，那这个数组就叫函数指针数组，那函数指针的数组如何定义呢？
//那要把函数的地址存到⼀个数组中，那这个数组就叫函数指针数组，那函数指针的数组如何定义呢？
//int (*parr1[3])();
//int* parr2[3]();
//int (*)() parr3[3];
//答案是：parr1
//parr1 先和[] 结合，说明parr1是数组，数组的内容是什么呢？
//是 int (*)() 类型的函数指针
//6. 转移表
//函数指针数组的⽤途：转移表
//举例：计算器的⼀般实现：
//int add(int x, int y)
//{
//	return x + y;
//}
//int sub(int x, int y)
//{
//	return x - y;
//}
//int mul(int x, int y)
//{
//	return x * y;
//}
//int div(int x, int y)
//{
//	return x / y;
//}
//int main()
//{
//	int x, y = 0;
//	int input = 1;
//	int ret = 0;
//	do
//	{
//		printf("********************************\n");
//		printf("****1.add**************2.sub****\n");
//		printf("****3.mul**************4.div****\n");
//		printf("*************0.exit*************\n");
//		printf("请选择：");
//		scanf("%d", &input);
//		switch (input)
//		{
//		case 1:
//			printf("请输入操作数:\n");
//			scanf("%d %d", &x, &y);
//			ret = add(x, y);
//			printf("%d\n", ret);
//			break;
//		case 2:
//			printf("请输入操作数:\n");
//			scanf("%d %d", &x, &y);
//			ret = sub(x, y);
//			printf("%d\n", ret);
//			break;
//		case 3:
//			printf("请输入操作数:\n");
//			scanf("%d %d", &x, &y);
//			ret = mul(x, y);
//			printf("%d\n", ret);
//			break;
//		case 4:
//			printf("请输入操作数:\n");
//			scanf("%d %d", &x, &y);
//			ret = div(x, y);
//			printf("%d\n", ret);
//			break;
//		case 0:
//			printf("退出计算器\n");
//			break;
//		default:
//			printf("输入错误，请重新输入\n");
//			break;
//		}
//	} while (input);
//	return 0;
//}
//利用函数指针数组来实现
//int add(int x, int y)
//{
//	return x + y;
//}
//int sub(int x, int y)
//{
//	return x - y;
//}
//int mul(int x, int y)
//{
//	return x * y;
//}
//int div(int x, int y)
//{
//	return x / y;
//}
//int main()
//{
//	int x, y = 0;
//	int input = 1;
//	int ret = 0;
//	int(*p[5])(int x,int y) = {0,add,sub,mul,div};
//	do
//	{
//		printf("********************************\n");
//		printf("****1.add**************2.sub****\n");
//		printf("****3.mul**************4.div****\n");
//		printf("*************0.exit*************\n");
//		printf("请选择：");
//		scanf("%d", &input);
//		if (input >= 1 && input <= 4)
//		{
//			printf("请输入操作数：\n");
//			scanf("%d %d", &x, &y);
//			ret = (*p[input])(x, y);
//			printf("ret = %d\n", ret);
//		}
//		else if (input == 0)
//		{
//			printf("退出计算器\n");
//		}
//		else
//		{
//			printf("输入有误\n");
//		}
//	} while (input);
//	return 0;
//}
//深入理解指针5
//1回调函数是什么
//回调函数就是⼀个通过函数指针调⽤的函数。
//如果你把函数的指针（地址）作为参数传递给另⼀个函数，当这个指针被⽤来调⽤其所指向的函数
//时，被调⽤的函数就是回调函数。回调函数不是由该函数的实现⽅直接调⽤，⽽是在特定的事件或条
//件发⽣时由另外的⼀⽅调⽤的，⽤于对该事件或条件进⾏响应。
//第13讲中我们写的计算机的实现的代码中，红⾊框中的代码是重复出现的，其中虽然执⾏计算的逻辑
//是区别的，但是输⼊输出操作是冗余的，有没有办法，简化⼀些呢？
//因为红⾊框中的代码，只有调⽤函数的逻辑是有差异的，我们可以把调⽤的函数的地址以参数的形式
//传递过去，使⽤函数指针接收，函数指针指向什么函数就调⽤什么函数，这⾥其实使⽤的就是回调函
//数的功能。

//int add(int x, int y)
//{
//	return x + y;
//}
//int sub(int x, int y)
//{
//	return x - y;
//}
//int mul(int x, int y)
//{
//	return x * y;
//}
//int div(int x, int y)
//{
//	return x / y;
//}
//void call(int(*p)(int x, int y))
//{
//	int x, y = 0;
//	int ret = 0;
//	printf("请输入操作数:\n");
//	scanf("%d %d", &x, &y);
//	ret = p(x, y);
//	printf("%d\n", ret);
//}
//int main()
//{
//	int input = 1;
//	do
//	{
//		printf("********************************\n");
//		printf("****1.add**************2.sub****\n");
//		printf("****3.mul**************4.div****\n");
//		printf("*************0.exit*************\n");
//		printf("请选择：");
//		scanf("%d", &input);
//		switch (input)
//		{
//		case 1:
//			call(add);
//			break;
//		case 2:
//			call(sub);
//			break;
//		case 3:
//			call(mul);
//			break;
//		case 4:
//			call(div);
//			break;
//		case 0:
//			printf("退出计算器\n");
//			break;
//		default:
//			printf("输入错误，请重新输入\n");
//			break;
//		}
//	} while (input);
//	return 0;
//}
//2. qsort使⽤举例
//2.1 使⽤qsort函数排序整型数据
//qsort是C语言中提供的排序函数
//quick qsort 是基于快速排序的算法的一种思想的一种排序方法，可以排序任意类型的数据
//void qsort(void* base,//指针指向的被排序数组的第一个元素
//	      size_t num, //base指向的被排序数组的元素个数
//	      size_t size //base指向的被排序数组的大小（长度），单位是字节
//	      int (*compar)(const void*, const void*) 
//          );  //*compar：函数指针，指针指向的函数是用来比较被排序数组中的两个元素
// 使用qsort函数的人，根据自己的需要，编写一个函数，用来排序自己的数据
//这⾥需要补充介绍结构指针和结构体成员访问操作符->；
//struct stu
//{
//	char name[20];
//	int age;
//};
////假设按照年龄来⽐较
//int cmp_stu_by_age(const void* e1, const void* e2)
//{
//	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
//}
////strcmp - 是库函数，是专⻔⽤来⽐较两个字符串的⼤⼩的 
////假设按照名字来⽐较 
//int cmp_stu_by_name(const void* e1, const void* e2)
//{
//	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
//}
////按照年龄来排序 
//void test2()
//{
//	struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
//	int sz = sizeof(s) / sizeof(s[0]);
//	qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
//}
//void test3()
//{
//	struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
//	int sz = sizeof(s) / sizeof(s[0]);
//	 qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
//}
//int main()
//{
//	test2();
//	test3();
//	return 0;
//}

//int main()
//{
//	return 0;
//}
//3. qsort函数的模拟实现
//使⽤回调函数，模拟实现qsort（采⽤冒泡的⽅式）。
//注意：这⾥第⼀次使⽤ void* 的指针，讲解 void* 的作⽤。
//#include <stdio.h>
//int int_cmp(const void* p1, const void* p2)
//{
//	return (*(int*)p1 - *(int*)p2);
//}
//void _swap(void* p1, void* p2, int size)
//{
//	int i = 0;
//	for (i = 0; i < size; i++)
//	{
//		char tmp = *((char*)p1 + i);
//		*((char*)p1 + i) = *((char*)p2 + i);
//		*((char*)p2 + i) = tmp;
//	}
//}
//void bubble(void* base, int count, int size, int(*cmp)(void*, void*))
//{
//	int i = 0;
//	int j = 0;
//	for (i = 0; i < count - 1; i++)
//	{
//		for (j = 0; j < count - i - 1; j++)
//		{
//			if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) >
//				0)
//			{
//			_swap((char*)base + j * size, (char*)base + (j + 1) * size,
//						size);
//			}
//		}
//	}
//}
//int main()
//{
//	int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
//	int i = 0;
//	bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
//	for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
//	{
//		printf("%d ", arr[i]);
//	}
//	printf("\n");
//	return 0;
//}
//深理解指针6
//1. sizeof和strlen的对⽐
//1.1 sizeof
//在学习操作符的时候，我们学习了 sizeof ， sizeof 计算变量所占内存空间⼤⼩的，单位是字
//节，如果操作数是类型的话，计算的是使⽤类型创建的变量所占内存空间的⼤⼩。
//sizeof 只关注占⽤内存空间的⼤⼩，不在乎内存中存放什么数据。
//#include <stdio.h>
//int main()
//{
//	int a = 10;
//	printf("%d\n", sizeof(a));
//	printf("%d\n", sizeof a);
//	printf("%d\n", sizeof(int));
//	return 0;
//}
//1.2 strlen
//strlen 是C语⾔库函数，功能是求字符串⻓度。函数原型如下：
//1 size_t strlen(const char* str); 
//统计的是从 strlen 函数的参数 str 中这个地址开始向后， \0 之前字符串中字符的个数。
//strlen 函数会⼀直向后找 \0 字符，直到找到为⽌，所以可能存在越界查找。
#include <stdio.h>
//int main()
//{
//	char arr1[3] = { 'a', 'b', 'c' };
//	char arr2[] = "abc";
//	printf("%zu\n", strlen(arr1));
//	printf("%zu\n", strlen(arr2));
//
//	printf("%zu\n", sizeof(arr1));
//	printf("%zu\n", sizeof(arr2));
//	return 0;
//}

//1.3 sizeof和strlen的对⽐
//sizeof
//1. sizeof是操作符
//2. sizeof计算操作数所占内存的
//⼤⼩，单位是字节
//3. 不关注内存中存放什么数据
//strlen
//1. strlen是库函数，使⽤需要包含头⽂件 string.h 
//2. srtlen是求字符串⻓度的，统计的是 \0 之前字符的个数
//3. 关注内存中是否有 \0 ，如果没有 \0 ，就会持续往后找，可能
//会越界
//指针变量的大下
//x64 ；8个字节
//x86：4个字节
//int main()
//{
//	int a = 10;
//	int* p = &a;
//	int c = sizeof(p);
//	printf("%d", c);
//	return 0;
//}
//2. 数组和指针笔试题解析
//数组名的意义：
//1. sizeof(数组名)，这⾥的数组名表⽰整个数组，计算的是整个数组的⼤⼩。
//2. &数组名，这⾥的数组名表⽰整个数组，取出的是整个数组的地址
//3. 除此之外所有的数组名都表⽰⾸元素的地址。
//2.1 ⼀维数组 
//int main()
//{
//
//	int a[] = { 1,2,3,4 };
//	printf("%zu\n", sizeof(a));      //  16
//	printf("%zu\n", sizeof(a + 0));  //  8
//	printf("%zu\n", sizeof(*a));     //  4
//	printf("%zu\n", sizeof(a + 1));  //  8
//	printf("%zu\n", sizeof(a[1]));   //  4
//	printf("%zu\n", sizeof(&a));     //  8
//	printf("%zu\n", sizeof(*&a));    //  16
//	printf("%zu\n", sizeof(&a + 1));  //  8
//	printf("%zu\n", sizeof(&a[0]));   //   8
//	printf("%zu\n", sizeof(&a[0] + 1)); //  8
//	return 0;
//}
//
//2.2 字符数组
//代码1：
//#include <stdio.h>
//int main()
//{
//	char arr[] = { 'a','b','c','d','e','f' };
//	printf("%d\n", sizeof(arr));        // 6
//	printf("%d\n", sizeof(arr + 0));    // 8
//	printf("%d\n", sizeof(*arr));       // 1
//	printf("%d\n", sizeof(arr[1]));     // 1
//	printf("%d\n", sizeof(&arr));       // 8
//	printf("%d\n", sizeof(&arr + 1));   // 8
//	printf("%d\n", sizeof(&arr[0] + 1));// 8
//	return 0;
//}
//代码2：
#include <stdio.h>
#include <string.h>
//strlen:统计的是字符串前/0的字符个数
//strlen 是C语⾔库函数，功能是求字符串⻓度。函数原型如下：
//1 size_t strlen(const char* str); 
//统计的是从 strlen 函数的参数 str 中这个地址开始向后， \0 之前字符串中字符的个数。
//strlen 函数会⼀直向后找 \0 字符，直到找到为⽌，所以可能存在越界查找。
//int main()
//{
//	char arr[] = { 'a','b','c','d','e','f' };
//	printf("%zu\n", strlen(arr));              //6
//	printf("%zu\n", strlen(arr + 0));         //6
//	printf("%zu\n", strlen(*arr));           //错误
//	printf("%zu\n", strlen(arr[1]));        //错误
//	printf("%zu\n", strlen(&arr));         //错误
//	printf("%zu\n", strlen(&arr + 1));    //错误
//	printf("%zu\n", strlen(&arr[0] + 1));//5
//	return 0;
//}
//#include <stdio.h>
//int main()
//{
//	char arr[] = "abcdef";
//	printf("%zu\n", sizeof(arr));//7  注：字符串末尾还有一个/0
//	printf("%zu\n", sizeof(arr + 0));//8
//	printf("%zu\n", sizeof(*arr));//1
//	printf("%zu\n", sizeof(arr[1]));//1
//	printf("%zu\n", sizeof(&arr));//8
//	printf("%zu\n", sizeof(&arr + 1));//8
//	printf("%zu\n", sizeof(&arr[0] + 1));//8
//	return 0;
//}
//代码4：
//#include <stdio.h>
//#include <string.h>
//int main()
//{
//	char arr[] = "abcdef";
//	printf("%zu\n", strlen(arr));//6
//	printf("%zu\n", strlen(arr + 0));//6
//	printf("%d\n", strlen(*arr));//错误
//	printf("%d\n", strlen(arr[1]));//错误
//	printf("%d\n", strlen(&arr));//错误
//	printf("%d\n", strlen(&arr + 1));//错误
//	printf("%d\n", strlen(&arr[0] + 1));//5
//	return 0;
//}
//
//代码6：
//#include <stdio.h>
//#include <string.h>
//int main()
//{
//	char* p = "abcdef";
//	printf("%d\n", strlen(p));//6
//	printf("%d\n", strlen(p + 1));//5
//	printf("%d\n", strlen(*p));//错误
//	printf("%d\n", strlen(p[0]));//错误
//	printf("%d\n", strlen(&p));//错误
//	printf("%d\n", strlen(&p + 1));//错误
//	printf("%d\n", strlen(&p[0] + 1));//5
//	return 0;
//}
//2.3 ⼆维数组
//注：二维数组的首元素是一维数组
//#include <stdio.h>
//int main()
//
//{
//	int a[3][4] = { 0 };
//	printf("%zu\n", sizeof(a));//48
//	printf("%zu\n", sizeof(a[0][0]));//4
//    printf("%zu\n", sizeof(a[0]));//16
//	printf("%zu\n", sizeof(a[0] + 1));//8
//	printf("%zu\n", sizeof(*(a[0] + 1)));//4
//	printf("%zu\n", sizeof(a + 1));//8
//	printf("%zu\n", sizeof(*(a + 1)));//16
//	printf("%zu\n", sizeof(&a[0] + 1));//8
//	printf("%zu\n", sizeof(*(&a[0] + 1)));//16
//	printf("%zu\n", sizeof(*a));//16
//	printf("%zu\n", sizeof(a[3]));//16
//	return 0;
//}
//3. 指针运算笔试题解析
//3.1 题⽬1：
//#include <stdio.h>
//int main()
//{
//	int a[5] = { 1, 2, 3, 4, 5 };
//	int* ptr = (int*)(&a + 1);
//	printf("%d,%d", *(a + 1), *(ptr - 1));
//	return 0;
//}
////程序的结果是什么？  2，5
//3.2 题⽬2
 //在X86环境下 //假设结构体的⼤⼩是20个字节 
//程序输出的结果是啥？ 
//struct Test
//{
//	int Num;
//	char* pcName;
//	short sDate;
//	char cha[2];
//	short sBa[4];
//}*p = (struct Test*)0x100000;
//int main()
//{
//	printf("%p\n", p + 0x1);
//	printf("%p\n", (unsigned long)p + 0x1);
//	printf("%p\n", (unsigned int*)p + 0x1);
//	return 0;
//}
//输出结果：
//00100014
//00100001
//00100004
//3.3 题⽬3
//#include <stdio.h>
//int main()
//{
//	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
//	int* p;
//	p = a[0];
//	printf("%d", p[0]);
//	return 0;
//}
//这里的初始化用的是逗号表达式。逗号表达式的运算规则是从左到右计算各个子表达式，
//并且整个逗号表达式的值是最后一个子表达式的值。所以：
//(0, 1)的值为1。
//(2, 3)的值为3。
//(4, 5)的值为5。
//////输出结果：1
//题⽬4
//假设环境是x86环境，程序输出的结果是啥？ 
//#include <stdio.h>
//int main()
//{
//	int a[5][5];
//	int(*p)[4];
//	p = a;
//     printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
//	return 0;
//}
////输出结果：FFFFFFFC,-4
//3.5 题⽬5
//#include <stdio.h>
//int main()
//{
//	int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
//	int* ptr1 = (int*)(&aa + 1);
//	int* ptr2 = (int*)(*(aa + 1));
//	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
//	return 0;
//}
//2. * (ptr2 - 1)的计算
//aa + 1的运算：aa作为数组名，在表达式中会退化为指向首行的指针，类型为int(*)[5]。aa + 1会指向数组的第二行，也就是aa[1]。
//* (aa + 1)的结果：对aa + 1进行解引用操作，得到的是第二行数组aa[1]，此时它会退化为指向第二行首元素的指针，类型为int * ，指向的是aa[1][0]。
//ptr2的指向：ptr2指向的是aa[1][0]。
//ptr2 - 1的指向：ptr2 - 1会让指针向前移动一个int的长度，从而指向第一行的最后一个元素aa[0][4]，该元素的值为5
////输出结果：10，5
//3.6 题⽬6
//#include <stdio.h>
//int main()
//{
//	char* a[] = { "work","at","alibaba" };
//	char** pa = a;
//	pa++;//后置++：先使用在加1
//	printf("%s\n", *pa);
//	return 0;
//}
////输出结果：at
//3.7 题⽬7
#include <stdio.h>
int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	char** cp[] = { c + 3,c + 2,c + 1,c };
	char*** cpp = cp;
	printf("%s\n", **++cpp);
	printf("%s\n", *-- * ++cpp + 3);
	printf("%s\n", *cpp[-2] + 3);
    printf("%s\n", cpp[-1][-1] + 1);
	return 0;
}
//输出结果：

POINT
ER
ST
EW
1. 初始化数据结构
字符指针数组c：
运行
char* c[] = { "ENTER","NEW","POINT","FIRST" };

c[0]指向字符串"ENTER"。
c[1]指向字符串"NEW"。
c[2]指向字符串"POINT"。
c[3]指向字符串"FIRST"。
二级指针数组cp：
运行
char** cp[] = { c + 3, c + 2, c + 1, c };

cp[0]的值为c + 3，指向c[3]。"FIRST"
cp[1]的值为c + 2，指向c[2]。"POINT"
cp[2]的值为c + 1，指向c[1]。"NEW"
cp[3]的值为c，指向c[0]。"ENTER"
三级指针cpp：

运行
char*** cpp = cp;

初始时，cpp指向cp[0]。"ENTER"
2. 第一次输出：printf("%s\n", **++cpp);
++cpp的操作：cpp原本指向cp[0]，执行++cpp后，cpp指向cp[1]。
* *cpp的结果：
* cpp得到的是cp[1]的值，也就是c + 2，它指向c[2]。
再次解引用，得到c[2]所指向的字符串"POINT"。
输出结果：POINT
3. 第二次输出：printf("%s\n", *-- * ++cpp + 3);
++cpp的操作：cpp原本指向cp[1]，执行++cpp后，cpp指向cp[2]。
* cpp的结果： * cpp得到的是cp[2]的值，也就是c + 1，它指向c[1]。
-- * cpp的操作：将c + 1减 1，得到c，也就是指向c[0]。
* -- * cpp的结果：解引用得到c[0]所指向的字符串"ENTER"。
+ 3的操作：从"ENTER"的第 4 个字符开始输出，得到"ER"。
输出结果：ER
4. 第三次输出：printf("%s\n", *cpp[-2] + 3);
cpp[-2]的计算：cpp当前指向cp[2]，cpp[-2]相当于* (cpp - 2)，也就是cp[0]。
* cpp[-2]的结果：* cp[0]得到的是c[3]，它指向字符串"FIRST"。
+ 3的操作：从"FIRST"的第 4 个字符开始输出，得到"ST"。
输出结果：ST
5. 第四次输出：printf("%s\n", cpp[-1][-1] + 1);
cpp[-1]的计算：cpp当前指向cp[2]，cpp[-1]相当于* (cpp - 1)，也就是cp[1]。
cpp[-1][-1]的计算：cp[1]的值为c + 2，(c + 2)[-1]相当于 * (c + 2 - 1)，也就是c[1]，它指向字符串"NEW"。
+ 1的操作：从"NEW"的第 2 个字符开始输出，得到"EW"。
输出结果：EW
总结
第一次输出：POINT
第二次输出：ER
第三次输出：ST
第四次输出：EW